package com.google.code.sysarch.v4.vrml_loader;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

import com.google.code.sysarch.v4.graphical.TextureLoader;

public class VRML_Block {

	private VRML_Loader loader;
	private String blockname = "";
	private String blocktype = "";
	private HashMap<String, VRML_Block> subblocks = new HashMap<String, VRML_Block>();	
	private HashMap<String, String[]> attributes = new HashMap<String, String[]>();
	private VRML_Block parent;
	private VRML_Block lastBlock;
	private String definition = null;
	private boolean begun = false;
	private int counter = 0;
	
	VRML_Block(String name, String type, VRML_Loader loader) {
		this(name, type, loader, false);
	}
	
	VRML_Block(String name, String type, VRML_Loader loader, boolean begun) {
		this.blockname = name;
		this.blocktype = type;
		this.loader = loader;
		this.begun = begun;
		this.parent = null;
	}
	
	public String getName() {
		return this.blockname;
	}
	
	public String getType() {
		return this.blocktype;
	}
	
	public void setSubBlock(String name, VRML_Block block) {
		this.subblocks.put(name+counter, block);
		counter++;
	}
	
	public void setParent(VRML_Block block) {
		this.parent = block;
	}
	
	public VRML_Block getParent() {
		return this.parent;
	}
	
	public void parseVRML(FileInputStream inStream) throws IOException {
		int r;
		char c;
		String command = "";
		
		while ((r = inStream.read()) != -1) {
			c = (char)r;
			if (c == '#') {
				while ((char)r != "\n".charAt(0)) r = inStream.read();
			} else if (c == '[') {
				if (!eval(inStream, "[")) return;
				command = "";
			} else if (c == ']') {
				if (!eval(inStream, "]")) return;
				command = "";				
			} else if (c == '{') {			
				if (!eval(inStream, "{")) return;
				command = "";
			} else if (c == '}') {
				if (!eval(inStream, "}")) return;
				command = "";
			} else if (c == ' ' || c == "\n".charAt(0) || c == "\r".charAt(0) || c == "\t".charAt(0)) {
				if (!eval(inStream, command)) return;
				command = "";
			} else {
				command += ""+c;				
			}
		}
	}
	
	public boolean eval(FileInputStream inStream, String command) throws IOException {
		if (command.trim().equals("")) { return true; }
		try {
			if (loader.getRef().expected(command, blocktype)) {
				String type = loader.getRef().type(command, blocktype);			
				//System.out.println("Recognized "+command+" as "+type);
				if (type.equals("boolean")) {	
					String[] put = { "boolean", readParam(inStream).toUpperCase() };
					this.attributes.put(command, put);
				} else if (type.equals("float")) {
					String[] put = { "float", readParam(inStream) };
					this.attributes.put(command, put);
				} else if (type.length() > 7 && type.substring(0,6).equals("float[")) {
					int count = Integer.parseInt(type.substring(6,type.length()-1));
					String[] put = new String[count+1];
					put[0] = type;
					for (int i=1; i<=count; i++) {
						put[i] = readParam(inStream);
					}
					this.attributes.put(command, put);		
				} else if (type.equals("String")) {
					String param = readParam(inStream).trim();
					String[] put = { "String", param.substring(1, param.length()-1)};
					this.attributes.put(command, put);		
				} else if (type.equals("Definition")) {
					definition = readParam(inStream).trim();
					if (!begun) {
						loader.setDef(definition, this);
						//System.out.println("Stored Definition '"+definition+"' directly.");						
						definition = null;
					}					
				} else if (type.equals("Use")) {
					String name = readParam(inStream).trim();
					VRML_Block block = loader.getDef(name);
					if (block == null) {
						// TODO Do not ignore this error :)
						// VRML_Loader.error("Block definition '"+name+"' not found.");
					} else if (this.lastBlock == null) {
						if (!begun && this.parent != null && loader.getRef().expected(block.getName(), this.parent.getType())) {
							this.parent.setSubBlock(block.getName(), block);
							return false;
						} else {
							if (loader.getRef().expected(block.getName(), this.blocktype)) {
								this.subblocks.put(block.getName(), block);
							} else {
								System.out.println("'"+name+"' of type "+block.getName()+" in block "+this.blocktype);
								VRML_Loader.error("There is no block to use '"+name+"' on.");
							}
						}
					} else {
						if (loader.getRef().expected(block.getName(), this.lastBlock.getType())) {
							this.lastBlock.setSubBlock(block.getName(), block);
						}
					}
				} else if (type.equals("pointlist")) {
					this.attributes.put(command, parseList(inStream, "PointList"));
				} else if (type.equals("intlist")) {
					this.attributes.put(command, parseList(inStream, "IntList"));
				} else if (type.equals("begin")) {
					if (!begun) {
						begun = true;
					} else {
						VRML_Block block = new VRML_Block("_unknown", "_unknown", loader, true);
						block.setParent(this);
						block.parseVRML(inStream);		
						this.lastBlock = block;
					}
				} else if (type.equals("return")) {
					return false;
				} else {
					VRML_Block block = new VRML_Block(command, type, loader);
					subblocks.put(command+counter, block);
					counter++;
					if (definition != null) {
						loader.setDef(definition, block);
						//System.out.println("Stored Definition '"+definition+"' indirectly.");						
						definition = null;							
					}
					block.setParent(this);					
					block.parseVRML(inStream);
					this.lastBlock = block;
				}
			} else {
				//System.out.println("Found unrecognized command '"+command+"' in a '"+this.blocktype+"' block.");
			}
		} catch (NumberFormatException e) { 
			VRML_Loader.error("Block parsing threw unexpected NumberFormatException.");
			return false;
		}
		return true;
	}
	
	public String readParam(FileInputStream inStream) throws IOException {
		String param = "";
		int r;
		while ((r = inStream.read()) != -1) {
			if ((char)r == ' ' || "\n".equals(""+(char)r)) {
				if (param != "") {
					break;
				}
			} else {
				param += (char)r;
			}
		}
		return param;
	}
	
	public String[] parseList(FileInputStream inStream, String dataType) throws IOException {
		int r = inStream.read();
		String param = "";
		ArrayList<String> list = new ArrayList<String>();
		list.add(dataType);
		while ((char)r != '[') r = inStream.read();
		while ((r = inStream.read()) != ']') {
			if ((char) r == ',' || (char)r == ' ' || (char)r == "\n".charAt(0) || (char)r == "\r".charAt(0)) {
				if (!param.trim().equals("")) {
					list.add(param.trim());
					param = "";
				}				
			} else if ((char)r == '#') {
				while ((char)r != "\n".charAt(0)) r = inStream.read();
			} else {
				param += (char)r;				
			}
		}
		if (!param.trim().equals("")) {
			list.add(param.trim());
		}
		return list.toArray(new String[0]);
	}
	
	public void executeVRML(VRML_Shape shape) {
		try {		
			if (blocktype.equals("Shape")) {
				shape = new VRML_Shape();
				loader.getModel().addShape(shape);
				VRML_Block transform = parentFind("Transform");
				if (transform != null) {
					String[] params = transform.getAttribute("rotation");
					shape.rotate(Float.parseFloat(params[4]), Float.parseFloat(params[1]), Float.parseFloat(params[2]), Float.parseFloat(params[3]));
					params = transform.getAttribute("translation");
					shape.translate(Float.parseFloat(params[1]), Float.parseFloat(params[2]), Float.parseFloat(params[3]));
					params = transform.getAttribute("scale");
					shape.scale(Float.parseFloat(params[1]), Float.parseFloat(params[2]), Float.parseFloat(params[3]));					
				}
			} else if (blocktype.equals("Material")) {
				String[] params = this.attributes.get("diffuseColor");
				if (shape != null && params != null) {
					shape.setColor(Float.parseFloat(params[1]), Float.parseFloat(params[2]), Float.parseFloat(params[3]), 0);
				}
			} else if (blocktype.equals("ImageTexture")) {
				String[] params = this.attributes.get("url");				
				if (shape != null && params != null) {
					shape.setTexture(TextureLoader.get().getTexture("res/textures/"+params[1]));
				}
			} else if (blocktype.equals("Coordinate")) {
				String[] params = this.attributes.get("point");
				if (shape != null && params != null) {
					for (int i=1; i<params.length; i+=3) {
						shape.addPoint(Float.parseFloat(params[i]), Float.parseFloat(params[i+1]), Float.parseFloat(params[i+2]));
					}
				}
			} else if (blocktype.equals("TextureCoordinate")) {
				String[] params = this.attributes.get("point");
				if (shape != null && params != null) {
					for (int i=1; i<params.length; i+=2) {						
						shape.addTexture(Float.parseFloat(params[i]), Float.parseFloat(params[i+1]));
					}					
				}	
			} else if (blocktype.equals("CoordinateInterpolator")) {
				String[] key = this.attributes.get("key");
				String[] keyval = this.attributes.get("keyValue");
				if (key != null && keyval != null) {
					int morphlen = (keyval.length-1)/(key.length-1);					
					for (int k=0; k<key.length-1; k++) {
						ArrayList<Float[]> list = new ArrayList<Float[]>();
						for (int i=k*morphlen+1; i <= (k+1)*morphlen; i+=3) {
							Float[] vertex = { Float.parseFloat(keyval[i]), Float.parseFloat(keyval[i+1]), Float.parseFloat(keyval[i+2]) };
							list.add(vertex);
						}
						float[][] array = new float[3][list.size()];
						for (int i=0; i<list.size(); i++) {
							array[0][i] = list.get(i)[0];
							array[1][i] = list.get(i)[1];
							array[2][i] = list.get(i)[2];
						}
						list = null;
						loader.getModel().addMorph(array);
					}
					float[] keylist = new float[key.length-1];
					for (int i=1; i<key.length; i++) {
						keylist[i-1] = Float.parseFloat(key[i]);
					}			
					loader.getModel().setMorphKeys(keylist);
				}
			} else if (blocktype.equals("IndexedFaceSet")) {
				String[] params = this.attributes.get("coordIndex");
				int i = 0;
				if (shape != null && params != null) {
					ArrayList<Integer> face = new ArrayList<Integer>();
					while (++i < params.length) {
						if (params[i].equals("-1")) {
							shape.addFace(face.toArray(new Integer[0]));
							face.clear();
						} else {
							face.add(Integer.parseInt(params[i]));
						}
					}
				}
				params = this.attributes.get("texCoordIndex");
				i = 0;				
				if (shape != null && params != null) {
					ArrayList<Integer> face = new ArrayList<Integer>();
					while (++i < params.length) {
						if (params[i].equals("-1")) {
							shape.addTexFace(face.toArray(new Integer[0]));
							face.clear();
						} else {
							face.add(Integer.parseInt(params[i]));
						}
					}
				}				
			}

			Collection<VRML_Block> vals = this.subblocks.values();
			Iterator<VRML_Block> ite = vals.iterator();
			while (ite.hasNext()) {
				VRML_Block next = ite.next();
				next.setParent(this);
				next.executeVRML(shape);
			}
		} catch (NumberFormatException e) {
			e.printStackTrace();
			VRML_Loader.error("Unexpected NumberFormatException while executing the model data.");
		} catch (ArrayIndexOutOfBoundsException e) {
			e.printStackTrace();
			VRML_Loader.error("Unexpected ArrayIndexOutOfBoundsException while executing the model data.");
		}
	}
	
	public String[] getAttribute(String att) {
		String[] result = this.attributes.get(att);
		if (result == null) {
			return loader.getRef().getDefault(att, this.blocktype);
		} else {
			return result;
		}
	}
	
	public VRML_Block parentFind(String type) {
		VRML_Block search = this.parent;
		while (search != null) {
			if (search.getType() == type) {
				return search;
			}
			search = search.getParent();
		}
		return null;
	}
	
}
